﻿#include "stdafx.h"

#include "svgwidget.h"
#include "svgreader.h"
#include "utils.h"

#include <cmath>
#include <QMouseEvent>
#include <QPainter>
#include <QLineEdit>
#include <QMessageBox>
#include <QMenu>
#include <QtSvg/QSvgRenderer>

namespace
{
	constexpr char CANVAS_BACKGROUND_COLOR[] = "#F0F0F0";

	constexpr size_t RECT_END_POINT_NUM = 8;

	// 这个的顺序需要和 _RectEndPoint枚举 匹配
	constexpr Qt::CursorShape RECT_END_POINT_CURSOR[RECT_END_POINT_NUM + 1] = {
		Qt::SizeHorCursor,
		Qt::SizeHorCursor,
		Qt::SizeVerCursor,
		Qt::SizeVerCursor,
		Qt::SizeBDiagCursor,
		Qt::SizeBDiagCursor,
		Qt::SizeFDiagCursor,
		Qt::SizeFDiagCursor,
		Qt::ArrowCursor,
	};

	// 这个的顺序需要和 _RectEndPoint枚举 匹配, 不考虑最后一个
	// bottom 是 0， top 是 1， left 是 0， right 是 1
	constexpr QPointF RECT_END_POINT_POS[RECT_END_POINT_NUM] = {
		{0, 0.5}, {1, 0.5}, {0.5, 1}, {0.5, 0}, {0, 0}, {1, 1}, {0, 1}, {1, 0} };

	const QString TEXT_EDIT_QSS = QStringLiteral("background-color:#00000000; border: 0px; margin: 0px; outline: 0px");

	bool _IsValidQPointF(const QPointF& p)
	{
		return !std::isnan(p.x()) && !std::isnan(p.y());
	}

	bool _DoubleEq(double a, double b)
	{
		return a - b < 0.0001 && a - b > -0.0001;
	}

	bool _IsAddNormalShape(SvgOperationType op)
	{
		return op == SvgOperationType::AddLine || op == SvgOperationType::AddRect || op == SvgOperationType::AddEllipse || op == SvgOperationType::AddIsoscelesTriangle || op == SvgOperationType::AddHexagon || op == SvgOperationType::AddPentagon;
	}

	// 这里坐标是 bottom 是 0， top 是 1， left 是 0， right 是 1
	unique_ptr<SvgPolygon> _CreatePolygon(const std::vector<QPointF>& points, const QRectF& rect)
	{
		QPolygonF polygon;
		const double l = rect.left();
		const double b = rect.bottom();
		const double w = rect.width();
		const double h = rect.height();

		for (const auto& p : points)
			polygon.append(QPointF(l + w * p.x(), b - h * p.y()));

		return std::make_unique<SvgPolygon>(std::move(polygon));
	}
}

SvgWidget::SvgWidget(QWidget* parent)
	: QWidget(parent), m_textEdit(new QLineEdit(this)), m_svgGeneralRender(new QSvgRenderer(this))
{
	setFocusPolicy(Qt::StrongFocus);
	setMouseTracking(true);

	m_font.setPixelSize(24);

	_InitUi();
}

void SvgWidget::Init()
{
	emit CanvasSelected(m_canvas);
	emit OperationChanged(SvgOperationType::SelectShape);
	emit ColorChanged(m_innerColor, m_lineColor);
}

void SvgWidget::ChangeOperation(SvgOperationType operation)
{
	if (operation == m_operation)
		return;
	if (!m_selectedShapes.empty())
		m_selectedShapes.clear(); // todo: 不清空会怎么样
	// 给qt打补丁， qt 5.15.2 在 LineEdit 为空的时候，失去焦点不会触发 editFinished 信号，5.12.11 会, 文档上说的也是会
	if (!m_textEdit->isHidden())
		_HandleTextEdit();

	m_operation = operation;
	emit CanvasSelected(m_canvas);
	emit OperationChanged(m_operation);
	update();
}

void SvgWidget::SaveSvg(const QString& path)
{
	if (!_CheckSupport())
		return;

	if (!m_canvas.Save(path))
	{
		QMessageBox::warning(
			this,
			tr("Save Error"),
			tr("can't save"));
	}
}

void SvgWidget::OpenSvg(const QString& fileName)
{
	NewSvg(); // 清空之前的状态
	// 这样写其实有不合理的地方，一方面可能会解析两遍 svg 文件，另一方面是 其实如果第一次解析的时候发现 wps 不合法，其实就可以直接返回了，没必要再解析
	// 但是要解决 一 的话，需要将 xml 解析器写的不那么通用一点，感觉没必要
	// 解决 二 其实很简单，但是我对自己手写的 xml 解析器没有信心，加入正确的样张它也解析失败了呢...
	if (_OpenWpsSvg(fileName) || _OpenGeneralSvg(fileName))
		return;
	QMessageBox::warning(
		this,
		tr("Open Error"),
		tr("can't open the svg file"));
}

void SvgWidget::NewSvg()
{
	m_canvas.Clear();
	m_selectedShapes.clear();
	m_previewShape = nullptr;
	m_editingText = nullptr;
	ChangeCanvasSize(CANVAS_DEFAULT_WIDTH, CANVAS_DEFAULT_HEIGHT);
	ChangeCanvasBackground(CANVAS_DEFAULT_BACKGROUND);

	m_isGeneralSvg = false;
	emit CanvasSelected(m_canvas);

	_RePaint();
}

void SvgWidget::ChangeCanvasSize(double width, double height)
{
	m_canvas.ReSize(width, height);
	setMinimumSize(QSize(_CanvasWidth(), _CanvasHeight()));
	m_canvas.SetBasePoint(_CalCenterBasePoint());
	update(); // todo: 需要RePaint么
}

void SvgWidget::ChangeCanvasBackground(const QColor& color)
{
	m_canvas.SetBackground(color);
	_RePaint();
}

void SvgWidget::ChangeSelectedStrokeWidth(double width)
{
	ASSERT(m_selectedShapes.size() == 1 && dynamic_cast<SvgLine*>(m_selectedShapes[0]) != nullptr); // 目前只支持 直线
	const QRectF origin = m_selectedShapes[0]->VisualRect();
	m_selectedShapes[0]->Pen().setWidthF(width);
	_RePaint(m_selectedShapes[0]->VisualRect().united(origin)); // 单次修改局部优，连续修改分层优
}

void SvgWidget::ChangeSelectedLinePos(const QLineF& line)
{
	ASSERT(m_selectedShapes.size() == 1 && dynamic_cast<SvgLine*>(m_selectedShapes[0]) != nullptr); // 只支持 单条直线

	QRectF needRePaintRect = m_selectedShapes[0]->VisualRect();
	dynamic_cast<SvgLine*>(m_selectedShapes[0])->SetLine(line);
	needRePaintRect = needRePaintRect.united(m_selectedShapes[0]->VisualRect()); // todo: 这里其实能再优化下 移动范围很大的时候考虑分开重绘
	_RePaint(needRePaintRect); // 单次修改局部优，连续修改分层优
}

void SvgWidget::ChangeScale(double scale)
{
	if (m_scale == scale)
		return;
	m_scale = scale;
	setMinimumSize(QSize(_CanvasWidth(), _CanvasHeight()));
	m_canvas.SetScale(m_scale);
	m_canvas.SetBasePoint(_CalCenterBasePoint());

	if (!m_textEdit->isHidden())
	{
		ASSERT(m_editingText); // 虽然其实不用加，因为出问题了直接崩。。。
		_SetTextEdit(m_canvas.CanvasPosToWidgetPos(m_editingText->Pos()).toPoint());
	}

	_RePaint(); // 这里可能重绘了两次，因为缩放是可能改变 m_canvas::m_viewPort 的, 如果改变了会导致 update 也重绘一次
	emit ScaleChanged(m_scale);
}

void SvgWidget::ChangeColor(const QColor& color, SvgColorTarget target)
{
	if (target == SvgColorTarget::Line)
	{
		m_lineColor = color;
		if (!m_selectedShapes.empty())
		{
			QRectF needRePaintRect = m_selectedShapes[0]->VisualRect();
			for (const auto& shape : m_selectedShapes)
			{
				needRePaintRect = needRePaintRect.united(shape->VisualRect());
				shape->Pen().setColor(color);
			}
			_RePaint(needRePaintRect); // 局部优
		}
	}
	else
	{
		m_innerColor = color;
		if (!m_selectedShapes.empty())
		{
			QRectF needRePaintRect = m_selectedShapes[0]->VisualRect();
			for (const auto& shape : m_selectedShapes)
			{
				shape->Brush().setColor(color);
				needRePaintRect = needRePaintRect.united(shape->VisualRect());
			}
			_RePaint(needRePaintRect); // 局部优
		}
	}
}

void SvgWidget::ViewPortChanged()
{
	_RePaint();
}

void SvgWidget::paintEvent(QPaintEvent* event)
{
	if (m_isGeneralSvg) // 丑陋的实现
	{
		_PaintGeneralSvg(event);
		QWidget::paintEvent(event);
		return;
	}

	QPainter painter(this);
	painter.setRenderHint(QPainter::Antialiasing, true);

	m_canvas.Paint(painter, visibleRegion().boundingRect());

	painter.save();

	_PainterToCanvasPos(painter);
	// 绘制预览使用 canvas 坐标系
	_PaintPreview(painter);

	painter.restore();

	if (m_selectRect)
		m_selectRect->Paint(painter);
	// 绘制背景，遮挡画布外的图形
	_PaintBackground(painter);

	// 这个按照效果需要在 绘制背景 之后绘制, 所以先恢复 Canvas 坐标系，让绘制更简单
	_PaintSelectedShapeEffect(painter);

	QWidget::paintEvent(event);
}

void SvgWidget::contextMenuEvent(QContextMenuEvent* event)
{
	const bool notEmpty = !m_selectedShapes.empty();
	m_deleteShapeAct->setEnabled(notEmpty);
	m_shapeToBackAct->setEnabled(notEmpty);
	m_shapeToTopAct->setEnabled(notEmpty);
	m_copyAct->setEnabled(notEmpty);
	m_pasteAct->setEnabled(!m_copiedShapes.empty());
	m_contextMenu->exec(event->globalPos());

	// 在 ChangeShapeSize 状态右键，清除这个状
	if (m_operation == SvgOperationType::ChangeShapeSize)
	{
		m_operation = SvgOperationType::SelectShape;
		setCursor(RECT_END_POINT_CURSOR[static_cast<size_t>(_RectEndPoint::None)]);
	}
}

void SvgWidget::mousePressEvent(QMouseEvent* event)
{
	ASSERT(m_textEdit->isHidden() || m_operation == SvgOperationType::AddText);
	if (m_operation != SvgOperationType::SelectShape && m_operation != SvgOperationType::ChangeShapeSize)
		m_selectedShapes.clear();
	if (event->buttons() == Qt::MiddleButton)
	{
		ChangeOperation(SvgOperationType::SelectShape);
	}
	else if (event->buttons() & Qt::LeftButton)
	{
		// 中途按下第二键的情况
		if (event->buttons() != Qt::LeftButton)
		{
			// todo, 这里可能需要调整
			if (m_operation == SvgOperationType::MoveShape)
				m_operation = SvgOperationType::SelectShape;
		}

		m_pressedPos = m_canvas.WidgetPosToCanvasPos(event->localPos());
		switch (m_operation)
		{
		case SvgOperationType::AddLine:
		case SvgOperationType::AddRect:
		case SvgOperationType::AddEllipse:
		case SvgOperationType::AddIsoscelesTriangle:
		case SvgOperationType::AddPentagon:
		case SvgOperationType::AddHexagon:
			break;
		case SvgOperationType::AddFreeLine:
			m_freeLinePoints.push_back(m_pressedPos);
			update();
			break;
		case SvgOperationType::AddText:
		{
			_CreateTextShape(m_canvas.WidgetPosToCanvasPos(event->localPos()));
			_ShowTextEdit(event->localPos().toPoint());
			update();
			break;
		}
		case SvgOperationType::SelectShape:
		{
			m_mouseLastPos = m_pressedPos;
			const auto shape = m_canvas.GetShapeByCoordinate(m_mouseLastPos);
			if (shape)
			{
				// 进入这里表示点击后是选中图形的状态
				const auto iter = std::find(m_selectedShapes.begin(), m_selectedShapes.end(), shape);
				if (iter == m_selectedShapes.end())
				{
					// 选中了图形，且图形不是之前被框选的图形中的一个的情况
					_NewShapeSelected(shape);
					update();
				}
				// 有图形被选中，且鼠标保持按下的时候，切换到 移动 模式
				m_operation = SvgOperationType::MoveShape;
				m_canvas.MakeGroup(m_selectedShapes); // todo: 这里适当延后一点应该效率更高
			}
			else if (!m_selectedShapes.empty())
			{
				// 点击空白处，且之前选中了图形的情况
				emit CanvasSelected(m_canvas);
				m_selectedShapes.clear();
				update();
			}
			break;
		}
		case SvgOperationType::ChangeShapeSize:
		{
			m_mouseLastPos = m_pressedPos;
			m_canvas.MakeGroup(m_selectedShapes);
			break;
		}
		default:
			ASSERT(false);
		}
	}
	QWidget::mousePressEvent(event);
}

void SvgWidget::mouseReleaseEvent(QMouseEvent* event)
{
	if (event->button() == Qt::LeftButton)
	{
		const QPointF releasePos = m_canvas.WidgetPosToCanvasPos(event->localPos());

		switch (m_operation)
		{
		case SvgOperationType::SelectShape:
		{
			// 删除选框
			if (m_selectRect)
			{
				m_selectRect = nullptr;
				update();
			}
			break;
		}
		case SvgOperationType::MoveShape:
		{
			// move 模式只是暂时的，释放鼠标切换回 选中模式
			m_operation = SvgOperationType::SelectShape;
			m_canvas.ClearGroup();
			if (!m_selectedShapes.empty())
				emit SelectedShapesUpdated(m_selectedShapes);
			break;
		}
		case SvgOperationType::AddFreeLine:
		{
			auto path = SvgPath::PointsToPath(m_freeLinePoints);
			if (!path)
			{
				m_freeLinePoints.clear();
				update();
				break;
			}
			path->Pen().setColor(m_lineColor);
			path->Brush().setColor(Qt::transparent); // 老师要求: 默认不填充自由线
			_AddShapeToCanvas(std::move(path));
			m_freeLinePoints.clear();
			update();
			break;
		}
		case SvgOperationType::AddText:
			break;
		case SvgOperationType::ChangeShapeSize:
			if (!m_selectedShapes.empty())
			{
				emit SelectedShapesUpdated(m_selectedShapes);
				m_canvas.ClearGroup();
			}
			break;
		default:
		{
			ASSERT(_IsValidQPointF(m_pressedPos));
			ASSERT(_IsAddNormalShape(m_operation));
			m_previewShape = nullptr;
			if ((m_operation == SvgOperationType::AddLine && releasePos == m_pressedPos) // 直线时，不让添加单独的点
				|| (m_operation != SvgOperationType::AddLine  // 非直线时，不让图形的宽度或高度为0
					&& (_DoubleEq(releasePos.x(), m_pressedPos.x()) || _DoubleEq(releasePos.y(), m_pressedPos.y()))))
			{
				update();
				break;
			}
			_AddShapeToCanvas(_CreateShape(m_operation, m_pressedPos, releasePos));
			update();
			break;
		}
		}
	}
	m_pressedPos = INVALID_POINT_F;
	QWidget::mouseReleaseEvent(event);
}

void SvgWidget::mouseMoveEvent(QMouseEvent* event)
{
	if ((m_operation == SvgOperationType::SelectShape || m_operation == SvgOperationType::ChangeShapeSize)
		&& event->buttons() == Qt::NoButton && m_selectedShapes.size() == 1)
	{
		const QPointF mouse = m_canvas.WidgetPosToCanvasPos(event->localPos());
		// 这里用选框的端点判断，而不是图形本身
		m_shapeEndPoint = _PointInRectEndPoint(mouse, m_selectedShapes[0]->VisualRect());
		setCursor(RECT_END_POINT_CURSOR[static_cast<size_t>(m_shapeEndPoint)]);

		if (m_shapeEndPoint != _RectEndPoint::None)
			m_operation = SvgOperationType::ChangeShapeSize;
		else
			m_operation = SvgOperationType::SelectShape;
	}
	else if (event->buttons() == Qt::LeftButton)
	{
		switch (m_operation)
		{
		case SvgOperationType::SelectShape:
		{
			// 框选
			if (!m_selectRect)
				m_selectRect = _GetSelectRect(event->localPos());
			m_selectRect->SetRect(QRectF(m_selectRect->Rect().topLeft(), event->localPos()));

			m_selectedShapes = m_canvas.GetShapeByRect(QRectF(m_pressedPos, m_canvas.WidgetPosToCanvasPos(event->localPos())));

			if (m_selectedShapes.empty())
				emit CanvasSelected(m_canvas);
			else
				emit SelectedShapesUpdated(m_selectedShapes);

			update();
			break;
		}
		case SvgOperationType::MoveShape:
		{
			// 拖动的情况
			ASSERT(!m_selectedShapes.empty());
			ASSERT(_IsValidQPointF(m_mouseLastPos));

			const QPointF currentPos = m_canvas.WidgetPosToCanvasPos(event->localPos());
			const QPointF p = currentPos - m_mouseLastPos;
			m_canvas.TranslateGroup(p);
			m_mouseLastPos = currentPos;
			update();
			break;
		}
		case SvgOperationType::AddFreeLine:
		{
			m_freeLinePoints.push_back(m_canvas.WidgetPosToCanvasPos(event->localPos()));
			update();
			break;
		}
		case SvgOperationType::AddText:
		{
			break;
		}
		case SvgOperationType::ChangeShapeSize:
		{
			ASSERT(_IsValidQPointF(m_pressedPos));
			ASSERT(m_selectedShapes.size() == 1);
			const QPointF& pos = m_canvas.WidgetPosToCanvasPos(event->localPos());
			const QRectF newRect = _GetNewBoundingRect(pos - m_mouseLastPos);
			m_canvas.TranslateGroup(newRect);
			m_mouseLastPos = pos;
			update();
			break;
		}
		default:
		{
			ASSERT(_IsValidQPointF(m_pressedPos));
			ASSERT(_IsAddNormalShape(m_operation));
			m_previewShape = _CreateShape(m_operation, m_pressedPos, m_canvas.WidgetPosToCanvasPos(event->localPos())); // 其实现在可以通过调整图形大小实现了
			update();
			break;
		}
		}
	}
	QWidget::mouseMoveEvent(event);
}

void SvgWidget::showEvent(QShowEvent* event)
{
	QWidget::showEvent(event);
	setMinimumSize(QSize(_CanvasWidth(), _CanvasHeight()));
	m_canvas.SetBasePoint(_CalCenterBasePoint());
}

void SvgWidget::resizeEvent(QResizeEvent* event)
{
	m_canvas.SetBasePoint(_CalCenterBasePoint());
	QWidget::resizeEvent(event);
}

void SvgWidget::wheelEvent(QWheelEvent* event)
{
	const int delta = event->angleDelta().y();
	if (event->modifiers() == Qt::ControlModifier && delta != 0)
	{
		const double r = delta > 0 ? 1.1 : 0.9;
		ChangeScale(m_scale * r);
		event->accept();
		return;
	}
	QWidget::wheelEvent(event);
}

void SvgWidget::keyPressEvent(QKeyEvent* event)
{
	if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)
	{
		if (!m_selectedShapes.empty())
		{
			_DeleteSelectedShape();
		}
	}
	else if (event->key() == Qt::Key_C && event->modifiers() & Qt::ControlModifier)
	{
		_CopyShapes();
	}
	else if (event->key() == Qt::Key_V && event->modifiers() & Qt::ControlModifier)
	{
		_PasteShapes();
	}
	QWidget::keyPressEvent(event);
}

void SvgWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
	m_pressedPos = m_canvas.WidgetPosToCanvasPos(event->localPos());
	if (m_selectedShapes.size() != 1)
		return;

	const auto textShape = dynamic_cast<SvgText*>(m_selectedShapes[0]);
	if (textShape)
	{
		m_editingText = textShape;
		m_canvas.MakeGroup(m_selectedShapes);
		_ShowTextEdit(m_canvas.CanvasPosToWidgetPos(m_editingText->Pos()).toPoint());
		m_textEdit->setText(textShape->Text());
		return;
	}
	QWidget::mouseDoubleClickEvent(event);
}

void SvgWidget::_InitUi()
{
	// text edit
	m_textEdit->hide();
	m_textEdit->setStyleSheet(TEXT_EDIT_QSS);

	QPalette palette = m_textEdit->palette();
	palette.setColor(QPalette::Text, Qt::transparent);
	m_textEdit->setPalette(palette);

	connect(m_textEdit, &QLineEdit::textEdited, this, &SvgWidget::_LineEditInput);
	connect(m_textEdit, &QLineEdit::editingFinished, this, &SvgWidget::_HandleTextEdit);

	// context menu
	m_contextMenu = new QMenu(this);
	m_deleteShapeAct = new QAction(tr("Delete"), this);
	m_shapeToTopAct = new QAction(tr("Bring To Front"), this);
	m_shapeToBackAct = new QAction(tr("Send To Back"), this);
	m_pasteAct = new QAction(tr("Paste"), this);
	m_copyAct = new QAction(tr("Copy"), this);

	m_contextMenu->addAction(m_copyAct);
	m_contextMenu->addAction(m_pasteAct);
	m_contextMenu->addAction(m_deleteShapeAct);

	auto sep = new QAction(this);
	sep->setSeparator(true);
	m_contextMenu->addAction(sep);

	m_contextMenu->addAction(m_shapeToTopAct);
	m_contextMenu->addAction(m_shapeToBackAct);

	connect(m_deleteShapeAct, &QAction::triggered, this, &SvgWidget::_DeleteSelectedShape);
	connect(m_shapeToTopAct, &QAction::triggered, this, &SvgWidget::_BringSelectedShapeToFront);
	connect(m_shapeToBackAct, &QAction::triggered, this, &SvgWidget::_SendSelectedShapeToBack);
	connect(m_copyAct, &QAction::triggered, this, &SvgWidget::_CopyShapes);
	connect(m_pasteAct, &QAction::triggered, this, &SvgWidget::_PasteShapes);
}

QPoint SvgWidget::_CalCenterBasePoint() const
{
	const QRegion region = visibleRegion();
	const QRect visibleRect = region.boundingRect();
	if (visibleRect.width() < _CanvasWidth() && visibleRect.height() < _CanvasHeight())
		return { 0, 0 };
	int x = static_cast<int>((visibleRect.width() - _CanvasWidth()) / 2);
	int y = static_cast<int>((visibleRect.height() - _CanvasHeight()) / 2);
	return { x < 0 ? 0 : x, y < 0 ? 0 : y };
}

double SvgWidget::_CanvasWidth() const
{
	return m_canvas.Width() * m_scale;
}

double SvgWidget::_CanvasHeight() const
{
	return m_canvas.Height() * m_scale;
}

void SvgWidget::_PainterToCanvasPos(QPainter& painter) const
{
	painter.translate(m_canvas.GetBasePoint());
	painter.scale(m_scale, m_scale);
}

void SvgWidget::_PaintBackground(QPainter& painter) const
{
	const QPointF& basePoint = m_canvas.GetBasePoint();
	if (basePoint == QPointF(0, 0))
		return;

	painter.save();
	painter.setPen(CANVAS_BACKGROUND_COLOR);
	painter.setBrush(QBrush(CANVAS_BACKGROUND_COLOR));

	const int canvasHeight = static_cast<int>(_CanvasHeight());
	const int canvasWidth = static_cast<int>(_CanvasWidth());
	const int basePointX = static_cast<int>(basePoint.x());
	const int basePointY = static_cast<int>(basePoint.y());

	painter.drawRect(0, 0, width(), basePointY);
	painter.drawRect(0, basePointY + canvasHeight, width(), basePointY);
	painter.drawRect(0, basePointY, basePointX, canvasHeight);
	painter.drawRect(basePointX + canvasWidth, basePointY, basePointX, canvasHeight);
	painter.restore();
}

void SvgWidget::_PaintSelectedShapeEffect(QPainter& painter) const
{
	if (m_selectedShapes.empty())
		return;
	painter.save();


	if (m_selectedShapes.size() == 1)
	{
		_PainterToCanvasPos(painter);
		painter.setPen(Qt::blue);
		const QRectF rect = m_selectedShapes[0]->VisualRect();

		painter.drawRect(rect);
		painter.setBrush(Qt::blue);

		constexpr size_t effectCellSize = 4;
		constexpr size_t sizeHalf = effectCellSize / 2;
		const double l = rect.left();
		const double b = rect.bottom();
		const double w = rect.width();
		const double h = rect.height();

		for (auto pos : RECT_END_POINT_POS)
		{
			painter.drawRect(QRectF(l + pos.x() * w - sizeHalf,
				b - pos.y() * h - sizeHalf,
				effectCellSize, effectCellSize));
		}
	}
	else
	{
		std::vector<QRectF> rects;
		for (const auto& shape : m_selectedShapes)
			rects.emplace_back(shape->VisualRect());

		QPixmap pixmap(m_canvas.GetViewPort().size());
		pixmap.fill(Qt::transparent);
		ParallelPaint(rects, pixmap, [this](QPainter& painter)
			{
				m_canvas.PainterToViewPortPos(painter);
				painter.setPen(Qt::blue);
			});
		painter.drawPixmap(m_canvas.GetViewPort().topLeft(), pixmap);
	}

	painter.restore();
}

void SvgWidget::_PaintPreview(QPainter& painter) const
{
	if (m_previewShape)
		m_previewShape->Paint(painter);

	if (!m_freeLinePoints.empty())
		_PaintFreeLinePoints(painter);
}

void SvgWidget::_PaintFreeLinePoints(QPainter& painter) const
{
	painter.save();
	QPen pen;
	pen.setColor(m_lineColor);
	pen.setWidth(2);
	painter.setPen(pen);
	painter.drawPolyline(m_freeLinePoints.data(), static_cast<int>(m_freeLinePoints.size()));
	painter.restore();
}

unique_ptr<SvgShape> SvgWidget::_CreateShape(SvgOperationType type, const QPointF& p0, const QPointF& p1) const
{
	unique_ptr<SvgShape> shape;
	switch (type)
	{
	case SvgOperationType::AddEllipse:
		shape = std::make_unique<SvgEllipse>(CreateRectF(p0, p1));
		break;
	case SvgOperationType::AddLine:
		shape = std::make_unique<SvgLine>(QLineF(p0, p1));
		break;
	case SvgOperationType::AddRect:
		shape = std::make_unique<SvgRect>(CreateRectF(p0, p1));
		break;
	case SvgOperationType::AddIsoscelesTriangle:
	{
		const QRectF rect = QRectF(p0, p1);
		shape = _CreatePolygon({ {0, 0}, {1, 0}, {0.5, 1} }, rect);
		break;
	}
	case SvgOperationType::AddPentagon:
	{
		const QRectF rect = QRectF(p0, p1);
		shape = _CreatePolygon({ {0.5, 1}, {1, 0.667}, {0.8, 0}, {0.2, 0}, {0, 0.667} }, rect);
		break;
	}
	case SvgOperationType::AddHexagon:
	{
		const QRectF rect = QRectF(p0, p1);
		shape = _CreatePolygon({ {0.2, 1}, {0.8, 1}, {1, 0.5}, {0.8, 0}, {0.2, 0}, {0, 0.5} }, rect);
		break;
	}
	default:
		ASSERT(false);
		return nullptr;
	}
	shape->Brush().setColor(m_innerColor);
	shape->Pen().setColor(m_lineColor);
	return shape;
}

void SvgWidget::_HandleTextEdit()
{
	if (m_textEdit->isHidden())
		return;

	m_textEdit->hide();
	m_textEdit->clear();

	if (m_editingText->Text().isEmpty())
		m_canvas.DeleteShape(m_editingText);
	m_editingText = nullptr;
	m_selectedShapes.clear();
	m_canvas.ClearGroup();
	// 这个行为是和参考的网页一致，虽然我不是很理解。。。
	if (m_operation == SvgOperationType::AddText)
		ChangeOperation(SvgOperationType::SelectShape);
	setFocus();
}

void SvgWidget::_NewShapeSelected(SvgShape* shape)
{
	m_selectedShapes.clear();
	m_selectedShapes.push_back(shape);
	m_innerColor = shape->Brush().color();
	m_lineColor = shape->Pen().color();
	emit ColorChanged(m_innerColor, m_lineColor);
	emit SelectedShapesUpdated(m_selectedShapes);
}

unique_ptr<SvgRect> SvgWidget::_GetSelectRect(const QPointF& pos) const
{
	auto rect = std::make_unique<SvgRect>(QRectF(pos, pos));
	rect->Pen().setStyle(Qt::PenStyle::DotLine);
	rect->Brush().setColor(Qt::transparent);
	rect->Pen().setColor(Qt::darkCyan);
	return rect;
}

bool SvgWidget::_OpenWpsSvg(const QString& fileName)
{
	try
	{
		// 使用 GBK 编码, bug: 如果系统编码非 GBK 可能会有问题
		QTextCodec* pCodec = QTextCodec::codecForName("gb2312");
		if (!pCodec) return false;
		QByteArray arr = pCodec->fromUnicode(fileName);

		const xml::Elements elems = xml::ParseFile(arr.data());
		if (!elems.empty() && elems[0].name == "svg")
		{
			const xml::Element& svg = elems[0];
			const auto iter = svg.attrs.find("subset");
			if (iter != svg.attrs.end() && iter->second == "wps")
			{
				m_isGeneralSvg = false;
				SvgCanvasInfo canvasInfo = ReadSvg(elems);
				m_canvas.SetShapes(std::move(canvasInfo.shapes));
				m_canvas.SetBackground(canvasInfo.background);
				ChangeCanvasSize(canvasInfo.width, canvasInfo.height);
				m_canvas.RePaint();
				emit CanvasSelected(m_canvas);
				return true;
			}
		}
		return false;
	}
	catch (std::runtime_error& e)
	{
		QMessageBox::information(
			this,
			tr("Maybe Bug"),
			QString(
				"If you are now opening a correct svg file and it goes here, it means there is a bug, "
				"please talk to me about it. Thank you very much!\n"
				"error message is \"%1\"")
			.arg(e.what()));
		return false;
	}
}

bool SvgWidget::_OpenGeneralSvg(const QString& fileName)
{
	if (!m_svgGeneralRender->load(fileName))
		return false;

	m_isGeneralSvg = true;
	update();
	return true;
}

void SvgWidget::_PaintGeneralSvg(QPaintEvent* event)
{
	QPainter painter(this);
	QRectF expectedRect = m_svgGeneralRender->viewBoxF();
	const double widgetWidth = width();
	const double widgetHeight = height();

	const double widthZoom = expectedRect.width() / widgetWidth;
	const double heightZoom = expectedRect.height() / widgetHeight;

	const double zoom = std::max(widthZoom, heightZoom);

	expectedRect.setSize(expectedRect.size() / zoom);

	m_svgGeneralRender->render(&painter, expectedRect);
}

bool SvgWidget::_CheckSupport()
{
	if (m_isGeneralSvg)
	{
		QMessageBox::warning(
			this,
			tr("Error"),
			tr("General Svg not support this feature"));
	}
	return !m_isGeneralSvg;
}

void SvgWidget::_LineEditInput(const QString& text)
{
	ASSERT(m_editingText);
	m_editingText->SetText(m_textEdit->text());
	m_textEdit->setFixedSize(SvgText::ContentSize(text, m_editingText->Font()).toSize() * m_scale);
	m_canvas.GroupRePaint();
	update();
}

void SvgWidget::_DeleteSelectedShape()
{
	ASSERT(!m_selectedShapes.empty());
	QRectF needRePaintRect = m_selectedShapes[0]->VisualRect();
	for (const auto shape : m_selectedShapes)
		needRePaintRect = needRePaintRect.united(shape->VisualRect());
	m_canvas.DeleteShapes(m_selectedShapes);
	m_selectedShapes.clear();
	if (m_operation == SvgOperationType::ChangeShapeSize)
	{
		m_operation = SvgOperationType::SelectShape;
		setCursor(RECT_END_POINT_CURSOR[static_cast<size_t>(_RectEndPoint::None)]);
	}
	emit CanvasSelected(m_canvas);
	_RePaint(needRePaintRect); // 依旧是局部重绘好点
}

void SvgWidget::_BringSelectedShapeToFront()
{
	ASSERT(!m_selectedShapes.empty());
	m_canvas.BringShapesToFront(m_selectedShapes);
}

void SvgWidget::_SendSelectedShapeToBack()
{
	ASSERT(!m_selectedShapes.empty());
	m_canvas.SendShapesToBack(m_selectedShapes);
}

SvgWidget::_RectEndPoint SvgWidget::_PointInRectEndPoint(const QPointF& point, const QRectF& rect)
{
	for (size_t i = 0; i < RECT_END_POINT_NUM; ++i)
	{
		constexpr double delta = 3;

		const double endPointX = rect.left() + rect.width() * RECT_END_POINT_POS[i].x();
		const double endPointY = rect.bottom() - rect.height() * RECT_END_POINT_POS[i].y();
		if (QLineF(point, QPointF(endPointX, endPointY)).length() < delta)
			return static_cast<_RectEndPoint>(i);
	}
	return _RectEndPoint::None;
}

// 这里的处理有点绕，拖拽的时候需要选中一个端点，选中的时候记录状态(m_shapeEndPoint)，然后拖拽过程中根据选中的端点
// 进行处理但是这个选中的端点状态不是拖动过程中不变的，假设选中 “下端点(_RectEndPoint::Bottom)”，你往上拖动，外接矩
// 形的“下边”越过“上边”时选中的端点状态会变为选中“上边(_RectEndPoint::Top)”，这时候往下拖动越过下边，就又会变成下边，
// 左右同理。
// 与此同时，现在的图形大小时不能缩放到0的，这里主要是考虑到自由线，因为假设拖动框的矩形宽度到了0，自由线 记录的所有
// 坐标的 纵坐标 都会变成相等的，这样你重新放大就不能复原原图形了。所以宽度有一个最小的大小(minDistance), 如果调整后
// 矩形(返回值)大小小于最小大小(minDistance), 就会直接设置成 -minDistance, 直接变成越过线的状态(就是上一段的
// _RectEndPoint::Bottom 到 Top 的过程), 注意，宽度（上一段的例子其实是 高度，但其实一样) 的负值只会出现再状态改变
// 的那一次, 之后如果不越线的话，值会一直是正的，因为状态已经改变了。
// PS. 返回值矩形的宽度或者高度为负值的话，会导致图形按对应轴翻转
QRectF SvgWidget::_GetNewBoundingRect(const QPointF& d)
{
	ASSERT(m_selectedShapes.size() == 1);

	constexpr double minDistance = 0.1;
	QRectF rect = m_selectedShapes[0]->BoundingRect();

	auto left = [&]
	{
		if (rect.width() - d.x() > minDistance)
		{
			rect.setX(rect.x() + d.x());
			return false;
		}
		rect.setX(rect.right() + minDistance);
		return true;
	};

	auto right = [&]
	{
		const double width = rect.width() + d.x();
		if (width > minDistance)
		{
			rect.setWidth(width);
			return false;
		}
		rect.setWidth(-minDistance);
		return true;
	};

	auto top = [&]
	{
		if (rect.height() - d.y() > minDistance)
		{
			rect.setY(rect.y() + d.y());
			return false;
		}
		rect.setY(rect.bottom() + minDistance);
		return true;
	};

	auto bottom = [&]
	{
		const double height = rect.height() + d.y();
		if (height > minDistance)
		{
			rect.setHeight(height);
			return false;
		}
		rect.setHeight(-minDistance);
		return true;
	};

	switch (m_shapeEndPoint)
	{
	case _RectEndPoint::Left:
		if (left())
			m_shapeEndPoint = _RectEndPoint::Right;
		return rect;
	case _RectEndPoint::Right:
		if (right())
			m_shapeEndPoint = _RectEndPoint::Left;
		return rect;
	case _RectEndPoint::Top:
		if (top())
			m_shapeEndPoint = _RectEndPoint::Bottom;
		return rect;
	case _RectEndPoint::Bottom:
		if (bottom())
			m_shapeEndPoint = _RectEndPoint::Top;
		return rect;
	case _RectEndPoint::LeftBottom:
	{
		const bool leftOverflow = left();
		const bool bottomOverflow = bottom();
		if (leftOverflow && bottomOverflow)
			m_shapeEndPoint = _RectEndPoint::RightTop;
		else if (leftOverflow)
			m_shapeEndPoint = _RectEndPoint::RightBottom;
		else if (bottomOverflow)
			m_shapeEndPoint = _RectEndPoint::LeftTop;
		return rect;
	}
	case _RectEndPoint::RightTop:
	{
		const bool rightOverflow = right();
		const bool topOverFlow = top();
		if (rightOverflow && topOverFlow)
			m_shapeEndPoint = _RectEndPoint::LeftBottom;
		else if (rightOverflow)
			m_shapeEndPoint = _RectEndPoint::LeftTop;
		else if (topOverFlow)
			m_shapeEndPoint = _RectEndPoint::RightBottom;
		return rect;
	}
	case _RectEndPoint::LeftTop:
	{
		const bool leftOverflow = left();
		const bool topOverFlow = top();
		if (leftOverflow && topOverFlow)
			m_shapeEndPoint = _RectEndPoint::RightBottom;
		else if (leftOverflow)
			m_shapeEndPoint = _RectEndPoint::RightTop;
		else if (topOverFlow)
			m_shapeEndPoint = _RectEndPoint::LeftBottom;
		return rect;
	}
	case _RectEndPoint::RightBottom:
	{
		const bool rightOverflow = right();
		const bool bottomOverflow = bottom();
		if (rightOverflow && bottomOverflow)
			m_shapeEndPoint = _RectEndPoint::LeftTop;
		else if (rightOverflow)
			m_shapeEndPoint = _RectEndPoint::LeftBottom;
		else if (bottomOverflow)
			m_shapeEndPoint = _RectEndPoint::RightTop;
		return rect;
	}
	case _RectEndPoint::None:
		break;
	}
	ASSERT(false);
	return {};
}

void SvgWidget::_AddShapeToCanvas(unique_ptr<SvgShape> shape)
{
	m_selectedShapes.clear();
	m_selectedShapes.push_back(shape.get());
	m_canvas.AddShape(std::move(shape));
	emit SelectedShapesUpdated(m_selectedShapes);
}

void SvgWidget::_RePaint()
{
	m_canvas.RePaint();
	update();
}

void SvgWidget::_RePaint(const QRectF& rect)
{
	m_canvas.RePaint(rect);
	update();
}

void SvgWidget::_ShowTextEdit(QPoint pos) const
{
	_SetTextEdit(pos);
	m_textEdit->setFocus();
	m_textEdit->show();
}

void SvgWidget::_SetTextEdit(QPoint pos) const
{
	const QSize size = (SvgText::ContentSize(m_textEdit->text(), m_editingText->Font()) * m_scale).toSize();
	const QPoint p(pos.x() + 2, pos.y() - size.height() * 0.8);
	m_textEdit->setGeometry(QRect(p, size)); // bug: 缩放的情况下，还是会出现问题
	m_textEdit->setFixedSize(size);

	QFont font;
	font.setPixelSize(size.height());
	m_textEdit->setFont(font);
}

void SvgWidget::_CreateTextShape(const QPointF& pos)
{
	ASSERT(!m_editingText);
	auto textShape = std::make_unique<SvgText>(pos, m_font);
	textShape->Pen().setColor(m_lineColor);
	textShape->Brush().setColor(m_innerColor);

	m_editingText = textShape.get();
	m_selectedShapes.push_back(m_editingText);
	m_canvas.AddShape(std::move(textShape));
	m_canvas.MakeGroup(m_selectedShapes);
}

void SvgWidget::_CopyShapes()
{
	m_copiedShapes.clear();
	for (const auto shape : m_selectedShapes)
		m_copiedShapes.push_back(shape->Clone());
}

void SvgWidget::_PasteShapes()
{
	m_selectedShapes.clear();
	for (const auto& shape : m_copiedShapes)
	{
		auto copiedShape = shape->Clone();
		m_selectedShapes.push_back(copiedShape.get());
		m_canvas.AddShape(std::move(copiedShape));
	}
	update();
}
